/*
 * Copyright (C) 2012 CyberAgent
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.gpuimage;

import android.opengl.GLES20;

import com.android.gpuimage.util.FilterTools;
import com.android.gpuimage.util.TextureRotationUtil;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;


/**
 * A hardware-accelerated 9-hit box blur of an image
 *
 * scaling: for the size of the applied blur, default of 1.0
 */
public class GPUImageHDROESFilter extends GPUImageFilter {
	public static final String FRAGMENT_SHADER_V = FilterTools.getDecryptString("enhvaWN5Y2VkKmdvbmN/Z3oqbGZla34xAHp4b2ljeWNlZCpnb25jf2d6KmNkfjEAAH9kY2xleGcqeWtnemZveDhOKmNken9+Q2drbW9eb3J+f3hvMQAAfGt4c2NkbSp8b2k4Kn5vcn5/eG9JZWV4bmNka35vMQAAf2RjbGV4ZypsZmVrfip6a3hrZ1VIZXJIZn94VX5vcm9mVX1jbn5iMQB/ZGNsZXhnKmxmZWt+KnpreGtnVUhlckhmf3hVeWlrZm8xAAB8ZWNuKmdrY2QiIypxAAAqKmxmZWt+KnljZG1mb0VsbHlvfio3KnpreGtnVUhlckhmf3hVfm9yb2ZVfWNufmIqICp6a3hrZ1VIZXJIZn94VXlpa2ZvMQAqKmxmZWt+KnljZG1mb0llZXhuKjcqfm9yfn94b0llZXhuY2Rrfm8kciogKnpreGtnVUhlckhmf3hVeWlrZm8xACoqbGZla34qZG99RWxseW9+KjcqOiQxACoAKioqfG9pOSpseGttZ29kfkllZmV4Kjcqfm9yfn94bzhOImNken9+Q2drbW9eb3J+f3hvJip8b2k4InljZG1mb0llZXhuJip+b3J+f3hvSWVleG5jZGt+byRzKiAqemt4a2dVSGVySGZ/eFV5aWtmbyMjJHhtaDEAKipjZH4qYyo3KjoxACoqbGV4ImMxKmMqNio9MSpjISEjKnEAKioqbHhrbWdvZH5JZWZleCohNyp+b3J+f3hvOE4iY2R6f35DZ2ttb15vcn5/eG8mKnxvaTgiZ2tyInljZG1mb0llZXhuKicqZG99RWxseW9+Jio6JCMmKn5vcn5/eG9JZWV4bmNka35vJHMqICp6a3hrZ1VIZXJIZn94VXlpa2ZvIyMkeG1oMQAqKipseGttZ29kfkllZmV4KiE3Kn5vcn5/eG84TiJjZHp/fkNna21vXm9yfn94byYqfG9pOCJnY2QieWNkbWZvSWVleG4qISpkb31FbGx5b34mKjokMzMjJip+b3J+f3hvSWVleG5jZGt+byRzKiAqemt4a2dVSGVySGZ/eFV5aWtmbyMjJHhtaDEAKioqZG99RWxseW9+KiE3KnljZG1mb0VsbHlvfjEAKip3ACoqbWZVTHhrbUllZmV4KjcqfG9pPiJseGttZ29kfkllZmV4KiUqOz8kJio7JDojMQB3");

// 	  public static final String FRAGMENT_SHADER_V = "precision mediump float;\n" +
//            "precision mediump int;\n" +
//            "\n" +
//            "uniform sampler2D inputImageTexture;\n" +
//            "\n" +
//            "varying vec2 textureCoordinate;\n" +
//            "\n" +
//            "uniform float param_BoxBlur_texel_width;\n" +
//            "uniform float param_BoxBlur_scale;\n" +
//            "\n" +
//            "void main() {\n" +
//            "\n" +
//            "  float singleOffset = param_BoxBlur_texel_width * param_BoxBlur_scale;\n" +
//            "  float singleCoord = textureCoordinate.x * param_BoxBlur_scale;\n" +
//            "  float newOffset = 0.;\n" +
//            " \n" +
//            "   vec3 fragmentColor = texture2D(inputImageTexture, vec2(singleCoord, textureCoordinate.y * param_BoxBlur_scale)).rgb;\n" +
//            "  int i = 0;\n" +
//            "  for(i; i < 7; i++) {\n" +
//            "   fragmentColor += texture2D(inputImageTexture, vec2(max(singleCoord - newOffset, 0.), textureCoordinate.y * param_BoxBlur_scale)).rgb;\n" +
//            "   fragmentColor += texture2D(inputImageTexture, vec2(min(singleCoord + newOffset, 0.99), textureCoordinate.y * param_BoxBlur_scale)).rgb;\n" +
//            "   newOffset += singleOffset;\n" +
//            "  }\n" +
//            "  gl_FragColor = vec4(fragmentColor / 15., 1.0);\n" +
//            "}";

	public static final String FRAGMENT_SHADER_H = FilterTools.getDecryptString("enhvaWN5Y2VkKmdvbmN/Z3oqbGZla34xAHp4b2ljeWNlZCpnb25jf2d6KmNkfjEAAH9kY2xleGcqeWtnemZveDhOKmNken9+Q2drbW9eb3J+f3hvMQAAfGt4c2NkbSp8b2k4Kn5vcn5/eG9JZWV4bmNka35vMQAAf2RjbGV4ZypsZmVrfip6a3hrZ1VIZXJIZn94VX5vcm9mVWJvY21ifjEAf2RjbGV4ZypsZmVrfip6a3hrZ1VIZXJIZn94VXlpa2ZvMQAAfGVjbipna2NkIiMqcQAAKipsZmVrfip5Y2RtZm9FbGx5b34qNyp6a3hrZ1VIZXJIZn94VX5vcm9mVWJvY21ifiogKnpreGtnVUhlckhmf3hVeWlrZm8xACoqbGZla34qeWNkbWZvSWVleG4qNyp+b3J+f3hvSWVleG5jZGt+byRzKiAqemt4a2dVSGVySGZ/eFV5aWtmbzEAKipsZmVrfipkb31FbGx5b34qNyo6JDEAKioqACoqfG9pOSpseGttZ29kfkllZmV4Kjcqfm9yfn94bzhOImNken9+Q2drbW9eb3J+f3hvJip8b2k4In5vcn5/eG9JZWV4bmNka35vJHIqICp6a3hrZ1VIZXJIZn94VXlpa2ZvJip5Y2RtZm9JZWV4biMjJHhtaDEAKmNkfipjKjcqOjEAKmxleCJjMSpjKjYqPTEqYyEhIypxACoqbHhrbWdvZH5JZWZleCohNyp+b3J+f3hvOE4iY2R6f35DZ2ttb15vcn5/eG8mKnxvaTgifm9yfn94b0llZXhuY2Rrfm8kciogKnpreGtnVUhlckhmf3hVeWlrZm8mKmdrciJ5Y2RtZm9JZWV4bionKmRvfUVsbHlvfiYqOiQjIyMkeG1oMQAqKmx4a21nb2R+SWVmZXgqITcqfm9yfn94bzhOImNken9+Q2drbW9eb3J+f3hvJip8b2k4In5vcn5/eG9JZWV4bmNka35vJHIqICp6a3hrZ1VIZXJIZn94VXlpa2ZvJipnY2QieWNkbWZvSWVleG4qISpkb31FbGx5b34mKjokMzMjIyMkeG1oMQAqKmRvfUVsbHlvfiohNyp5Y2RtZm9FbGx5b34xACp3ACptZlVMeGttSWVmZXgqNyp8b2k+Imx4a21nb2R+SWVmZXgqJSo7PyQmKjskOiMxAHc=");

//    public static final String FRAGMENT_SHADE_H = "precision mediump float;\n" +
//            "precision mediump int;\n" +
//            "\n" +
//            "uniform sampler2D inputImageTexture;\n" +
//            "\n" +
//            "varying vec2 textureCoordinate;\n" +
//            "\n" +
//            "uniform float param_BoxBlur_texel_height;\n" +
//            "uniform float param_BoxBlur_scale;\n" +
//            "\n" +
//            "void main() {\n" +
//            "\n" +
//            "  float singleOffset = param_BoxBlur_texel_height * param_BoxBlur_scale;\n" +
//            "  float singleCoord = textureCoordinate.y * param_BoxBlur_scale;\n" +
//            "  float newOffset = 0.;\n" +
//            "   \n" +
//            "  vec3 fragmentColor = texture2D(inputImageTexture, vec2(textureCoordinate.x * param_BoxBlur_scale, singleCoord)).rgb;\n" +
//            " int i = 0;\n" +
//            " for(i; i < 7; i++) {\n" +
//            "  fragmentColor += texture2D(inputImageTexture, vec2(textureCoordinate.x * param_BoxBlur_scale, max(singleCoord - newOffset, 0.))).rgb;\n" +
//            "  fragmentColor += texture2D(inputImageTexture, vec2(textureCoordinate.x * param_BoxBlur_scale, min(singleCoord + newOffset, 0.99))).rgb;\n" +
//            "  newOffset += singleOffset;\n" +
//            " }\n" +
//            " gl_FragColor = vec4(fragmentColor / 15., 1.0);\n" +
//            "}";

    public static final String VERTEX_SHADER_T = "attribute vec4 position;\n" +
            "attribute vec4 inputTextureCoordinate;\n" +
            "attribute vec4 inputTextureCoordinate2;\n" +
            " \n" +
            "varying vec2 textureCoordinate;\n" +
            "varying vec2 textureCoordinate2;\n" +
            " \n" +
            "void main()\n" +
            "{\n" +
            "    gl_Position = position;\n" +
            "    textureCoordinate = inputTextureCoordinate.xy;\n" +
            "    textureCoordinate2 = inputTextureCoordinate2.xy;\n" +
            "}";
    public static final String FRAGMENT_SHADE_T = FilterTools.getDecryptString("enhvaWN5Y2VkKmdvbmN/Z3oqbGZla34xAAB/ZGNsZXhnKnlrZ3pmb3g4TipjZHp/fkNna21vXm9yfn94bzEAf2RjbGV4Zyp5a2d6Zm94OE4qY2R6f35DZ2ttb15vcn5/eG84MQAAfGt4c2NkbSp8b2k4Kn5vcn5/eG9JZWV4bmNka35vMQB8a3hzY2RtKnxvaTgqfm9yfn94b0llZXhuY2Rrfm84MQAAf2RjbGV4ZypsZmVrfip6a3hrZ1VCTlhVb2xsb2l+VXl+eG9kbX5iMQB/ZGNsZXhnKmxmZWt+KnpreGtnVUJOWFVta2dnazEAf2RjbGV4ZypsZmVrfip6a3hrZ1VCTlhVbXhremIxAAAqfG9pOSpta2dnayJsZmVrfip8a2Z/byYqKnxvaTkqaWVmZXgjKnEAKioqbGZla34qeio3KjokMiogKiJ8a2Z/byonKjskIyohKjskOjEAKioqbGZla34qayo3KiI7JConKnojKiUqOiQ/MQAqKipsZmVrfipoKjcqInoqJyo6JD8jKiUqOiQ/MQAqKip4b35/eGQqaWZrZ3oiayogKmllZmV4KiAqaWVmZXgqISpoKiAqaWVmZXgqJio6JCYqOyQjMQB3AABsZmVrfiprbmB/eX5vbiJsZmVrfipteGt6Yll+eG9kbX5iJipsZmVrfippZWZleCMqcQAqKipsZmVrfip8a2ZsKjcqaWZrZ3oiIjskKicqaWVmZXgjKiAqOCQmKjokKiYqOCQjMQAqKipsZmVrfipnf2Z+KjcqaWZrZ3oiemV9ImtoeSJ8a2ZsKicqOyQjJipteGt6Yll+eG9kbX5iKiAqOzokIyYqOiQmKjskIzEAKioqeG9+f3hkKmlma2d6IiIifGtmbConKjskIyogKmd/Zn4qISo7JCMmKjokJio4JCMxAHcAAGxmZWt+KmV8b3hueGN8byJsZmVrfip5fnhvZG1+YiYqbGZla34qaWVmZXgjKnEAKioqeG9+f3hkKmlma2d6IiJpZWZleConKjskIyogKnl+eG9kbX5iKiEqOyQmKjokJio4JCMxAHcAAGxmZWt+Kmh4Y21ifmRveXkibGZla34qeX54b2RtfmImKmxmZWt+KmllZmV4IypxACoqKmxmZWt+Kn4qNyp5fnhvZG1+YjEAKioqY2wqIn4qNjcqOyQ6IypxACoqKioqKnhvfn94ZCppZWZleCogKn4xACoqKncqb2Z5bypxACoqKioqKnhvfn94ZCppZWZleCogKiI4JConKn4jKiEqOyQqICoifionKjskIzEAKioqdwB3AABsZmVrfiprfG94a21vInxvaTkqaWVmZXgjKnEAKioqeG9+f3hkKiJpZWZleCRyKiEqaWVmZXgkcyohKmllZmV4JHAjKiUqOSQxAHcAAHxlY24qZ2tjZCIjKnEAKioqACoqKip8b2k5KmV4Y21JZWZleCo3Kn5vcn5/eG84TiJjZHp/fkNna21vXm9yfn94byYqfm9yfn94b0llZXhuY2Rrfm8jJHhtaDEAKioqKnxvaTkqaGZ/eEllZmV4Kjcqfm9yfn94bzhOImNken9+Q2drbW9eb3J+f3hvOCYqfm9yfn94b0llZXhuY2Rrfm84IyR4bWgxACoqKgAqKipsZmVrfiprfG0qNyprfG94a21vImhmf3hJZWZleCMxACoqKmt8bSo3Kmh4Y21ifmRveXkiemt4a2dVQk5YVW1rZ2drKiAqOCQmKmt8bSMxACoqKmt8bSo3KmtuYH95fm9uInpreGtnVUJOWFVteGt6YiYqa3xtIzEAKioqa3xtKjcqZXxveG54Y3xvInpreGtnVUJOWFVvbGxvaX5VeX54b2RtfmIqICoyJCYqa3xtIzEAKioqbWZVTHhrbUllZmV4KjcqfG9pPiJta2dnayJrfG0mKmV4Y21JZWZleCMmKjskIzEAdw==");
//    public static final String FRAGMENT_SHADE_T = "precision mediump float;\n" +
//            "\n" +
//            "uniform sampler2D inputImageTexture;\n" +
//            "uniform sampler2D inputImageTexture2;\n" +
//            "\n" +
//            "varying vec2 textureCoordinate;\n" +
//            "varying vec2 textureCoordinate2;\n" +
//            "\n" +
//            "uniform float param_HDR_effect_strength;\n" +
//            "uniform float param_HDR_gamma;\n" +
//            "uniform float param_HDR_graph;\n" +
//            "\n" +
//            " vec3 gamma(float value,  vec3 color) {\n" +
//            "   float p = 0.8 * (value - 1.) + 1.0;\n" +
//            "   float a = (1. - p) / 0.5;\n" +
//            "   float b = (p - 0.5) / 0.5;\n" +
//            "   return clamp(a * color * color + b * color , 0., 1.);\n" +
//            "}\n" +
//            "\n" +
//            "float adjusted(float graphStrength, float color) {\n" +
//            "   float valf = clamp((1. - color) * 2., 0. , 2.);\n" +
//            "   float mult = clamp(pow(abs(valf - 1.), graphStrength * 10.), 0., 1.);\n" +
//            "   return clamp(((valf - 1.) * mult + 1.), 0., 2.);\n" +
//            "}\n" +
//            "\n" +
//            "float overdrive(float strength, float color) {\n" +
//            "   return clamp((color - 1.) * strength + 1., 0., 2.);\n" +
//            "}\n" +
//            "\n" +
//            "float brightness(float strength, float color) {\n" +
//            "   float t = strength;\n" +
//            "   if (t <= 1.0) {\n" +
//            "      return color * t;\n" +
//            "   } else {\n" +
//            "      return color * (2. - t) + 1. * (t - 1.);\n" +
//            "   }\n" +
//            "}\n" +
//            "\n" +
//            "float average(vec3 color) {\n" +
//            "   return (color.x + color.y + color.z) / 3.;\n" +
//            "}\n" +
//            "\n" +
//            "void main() {\n" +
//            "   \n" +
//            "    vec3 origColor = texture2D(inputImageTexture, textureCoordinate).rgb;\n" +
//            "    vec3 blurColor = texture2D(inputImageTexture2, textureCoordinate2).rgb;\n" +
//            "   \n" +
//            "   float avg = average(blurColor);\n" +
//            "   avg = brightness(param_HDR_gamma * 2., avg);\n" +
//            "   avg = adjusted(param_HDR_graph, avg);\n" +
//            "   avg = overdrive(param_HDR_effect_strength * 8., avg);\n" +
//            "   gl_FragColor = vec4(gamma(avg, origColor), 1.);\n" +
//            "}";

    private int[] mFrameBuffers;
    private int[] mFrameBufferTextures;

    private FloatBuffer mGLCubeBuffer;
    private FloatBuffer mGLTextureBuffer;
    private final FloatBuffer mGLTextureFlipBuffer;
    private FloatBuffer mGLTextureScaleBuffer;

    public int mFilterSecondTextureCoordinateAttribute;
    public int mFilterInputTextureUniform2;
    public int mFilterSourceTexture2 = OpenGlUtils.NO_TEXTURE;

    private GPUImageFilter mNoFilter;
    private GPUImageFilter mVerticalFilter;
    private GPUImageFilter mHorizontalFilter;

    private float mBlurScale = 2.5f;

    private float mEffectStrength = 0.2f;
    private float mGamma = 0.54f;
    private float mGraph = 0.2f;

    public GPUImageHDROESFilter() {
        super(VERTEX_SHADER_T, FRAGMENT_SHADE_T);
        mNoFilter = new GPUImageOESFilter();
        mVerticalFilter = new GPUImageFilter(NO_FILTER_VERTEX_SHADER, FRAGMENT_SHADER_V);
        mHorizontalFilter = new GPUImageFilter(NO_FILTER_VERTEX_SHADER, FRAGMENT_SHADER_H);
        mGLCubeBuffer = ByteBuffer.allocateDirect(GPUImageRenderer.CUBE.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        mGLCubeBuffer.put(GPUImageRenderer.CUBE).position(0);

        mGLTextureBuffer = ByteBuffer.allocateDirect(TextureRotationUtil.TEXTURE_NO_ROTATION.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        mGLTextureBuffer.put(TextureRotationUtil.TEXTURE_NO_ROTATION).position(0);

        float[] flipTexture = TextureRotationUtil.getRotation(Rotation.NORMAL, false, true);
        mGLTextureFlipBuffer = ByteBuffer.allocateDirect(flipTexture.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        mGLTextureFlipBuffer.put(flipTexture).position(0);
    }

    @Override
    public void onInit() {
        super.onInit();
        mFilterSecondTextureCoordinateAttribute = GLES20.glGetAttribLocation(getProgram(), "inputTextureCoordinate2");
        mFilterInputTextureUniform2 = GLES20.glGetUniformLocation(getProgram(), "inputImageTexture2"); // This does assume a name of "inputImageTexture2" for second input texture in the fragment shader
        GLES20.glEnableVertexAttribArray(mFilterSecondTextureCoordinateAttribute);

        mNoFilter.onInit();
        mVerticalFilter.onInit();
        mHorizontalFilter.onInit();
    }

    protected void initTexelOffsets() {
        int texelWidthOffsetLocation = GLES20.glGetUniformLocation(mVerticalFilter.getProgram(), "param_BoxBlur_texel_width");
        int blurSacleLocation = GLES20.glGetUniformLocation(mVerticalFilter.getProgram(), "param_BoxBlur_scale");
        mVerticalFilter.setFloat(texelWidthOffsetLocation, 1 / mOutputWidth);
        mVerticalFilter.setFloat(blurSacleLocation, mBlurScale);

        int texelHeightOffsetLocation = GLES20.glGetUniformLocation(mHorizontalFilter.getProgram(), "param_BoxBlur_texel_height");
        blurSacleLocation = GLES20.glGetUniformLocation(mHorizontalFilter.getProgram(), "param_BoxBlur_scale");
        mHorizontalFilter.setFloat(texelHeightOffsetLocation, 1 / mOutputHeight);
        mHorizontalFilter.setFloat(blurSacleLocation, mBlurScale);

        int effectStrengthLocation = GLES20.glGetUniformLocation(getProgram(), "param_HDR_effect_strength");
        int gammaLocation = GLES20.glGetUniformLocation(getProgram(), "param_HDR_gamma");
        int graphLocation = GLES20.glGetUniformLocation(getProgram(), "param_HDR_graph");
        setFloat(effectStrengthLocation, mEffectStrength);
        setFloat(gammaLocation, mGamma);
        setFloat(graphLocation, mGraph);
    }

    @Override
    public void onOutputSizeChanged(int width, int height) {
        super.onOutputSizeChanged(width, height);
        initTexelOffsets();
        destroyFramebuffers();
        mNoFilter.onOutputSizeChanged(width, height);
        mVerticalFilter.onOutputSizeChanged(width, height);
        mHorizontalFilter.onOutputSizeChanged(width, height);

        mFrameBuffers = new int[4];
        mFrameBufferTextures = new int[4];

        // FrameBuffer1
        GLES20.glGenFramebuffers(1, mFrameBuffers, 0);
        GLES20.glGenTextures(1, mFrameBufferTextures, 0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mFrameBufferTextures[0]);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, (int) (width), (int) (height), 0,
                GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[0]);
        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
                GLES20.GL_TEXTURE_2D, mFrameBufferTextures[0], 0);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

        // FrameBuffer2
        GLES20.glGenFramebuffers(1, mFrameBuffers, 1);
        GLES20.glGenTextures(1, mFrameBufferTextures, 1);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mFrameBufferTextures[1]);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, (int) (width), (int) (height), 0,
                GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[1]);
        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
                GLES20.GL_TEXTURE_2D, mFrameBufferTextures[1], 0);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

        // FrameBuffer3
        GLES20.glGenFramebuffers(1, mFrameBuffers, 2);
        GLES20.glGenTextures(1, mFrameBufferTextures, 2);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mFrameBufferTextures[2]);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, (int) (width), (int) (height), 0,
                GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[2]);
        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
                GLES20.GL_TEXTURE_2D, mFrameBufferTextures[2], 0);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

        // FrameBuffer4
        GLES20.glGenFramebuffers(1, mFrameBuffers, 3);
        GLES20.glGenTextures(1, mFrameBufferTextures, 3);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mFrameBufferTextures[3]);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, (int) (width), (int) (height), 0,
                GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[3]);
        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
                GLES20.GL_TEXTURE_2D, mFrameBufferTextures[3], 0);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

        float[] t = {
                0f, 1f - 1f / mBlurScale,
                1f / mBlurScale / mBlurScale, 1f - 1f / mBlurScale,
                0f, 1f - 1f / mBlurScale + 1f / mBlurScale / mBlurScale,
                1f / mBlurScale / mBlurScale, 1f - 1f / mBlurScale + 1f / mBlurScale / mBlurScale,
        };

        mGLTextureScaleBuffer = ByteBuffer.allocateDirect(t.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        mGLTextureScaleBuffer.put(t).position(0);
    }

    @Override
    protected void onDrawArraysPre() {
        GLES20.glEnableVertexAttribArray(mFilterSecondTextureCoordinateAttribute);
        GLES20.glActiveTexture(GLES20.GL_TEXTURE3);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mFilterSourceTexture2);
        GLES20.glUniform1i(mFilterInputTextureUniform2, 3);

        GLES20.glVertexAttribPointer(mFilterSecondTextureCoordinateAttribute, 2, GLES20.GL_FLOAT, false, 0, mGLTextureScaleBuffer);
    }

    @Override
    public void onDraw(final int textureId, final FloatBuffer cubeBuffer,
                       final FloatBuffer textureBuffer) {
        runPendingOnDrawTasks();
        if (!isInitialized() || mFrameBuffers == null || mFrameBufferTextures == null) {
            return;
        }

        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[0]);
        GLES20.glClearColor(0, 0, 0, 0);
        mNoFilter.onDraw(textureId, cubeBuffer, textureBuffer);

        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[1]);
        GLES20.glClearColor(0, 0, 0, 0);
        mVerticalFilter.onDraw(mFrameBufferTextures[0], mGLCubeBuffer, mGLTextureBuffer);

        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[2]);
        GLES20.glClearColor(0, 0, 0, 0);
        mHorizontalFilter.onDraw(mFrameBufferTextures[1], mGLCubeBuffer, mGLTextureBuffer);

        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
        mFilterSourceTexture2 = mFrameBufferTextures[2];
        super.onDraw(mFrameBufferTextures[0], mGLCubeBuffer, mGLTextureFlipBuffer);
    }

    @Override
    public void onDestroy() {
        destroyFramebuffers();
        super.onDestroy();
    }

    private void destroyFramebuffers() {
        if (mFrameBufferTextures != null) {
            GLES20.glDeleteTextures(mFrameBufferTextures.length, mFrameBufferTextures, 0);
            mFrameBufferTextures = null;
        }
        if (mFrameBuffers != null) {
            GLES20.glDeleteFramebuffers(mFrameBuffers.length, mFrameBuffers, 0);
            mFrameBuffers = null;
        }
    }
}
